package com.hongyun.tms.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.hongyun.tms.common.PageResponseDto;
import com.hongyun.tms.common.constants.DefaultCreateByConstant;
import com.hongyun.tms.common.constants.ProdPlanStatusConstant;
import com.hongyun.tms.common.constants.RateConstant;
import com.hongyun.tms.common.constants.RedisKeyConstant;
import com.hongyun.tms.common.dto.pordwork.PordWorkExportDto;
import com.hongyun.tms.common.dto.prodplan.*;
import com.hongyun.tms.common.enums.BizCode;
import com.hongyun.tms.common.utils.DateUtils;
import com.hongyun.tms.common.utils.GenerateNumberUtil;
import com.hongyun.tms.common.utils.MapperingUtils;
import com.hongyun.tms.common.vo.PordWorkVO;
import com.hongyun.tms.common.vo.ProPlanVO;
import com.hongyun.tms.entity.*;
import com.hongyun.tms.exception.BusinessRuntimeException;
import com.hongyun.tms.mapper.ProdPlanMapper;
import com.hongyun.tms.service.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 作业计划 服务实现类
 * </p>
 *
 * @author liPeng
 * @since 2024-06-12
 */
@Service
public class ProdPlanServiceImpl extends ServiceImpl<ProdPlanMapper, ProdPlan> implements IProdPlanService {

    @Resource
    private IPordWorkService workService;

    @Resource
    private IMShipService shipService;

    @Resource
    private IMRateService rateService;

    @Resource
    private ISysFileBusinessService fileBusinessService;

    @Override
//    @DS("mysql")
    public PageResponseDto<ProPlanVO> treeList(ProPlanQueryDto query) {
        Page<ProdPlan> page = new Page<>(query.getStartPage(), query.getPageSize());
        // 创建查询参数
        ProPlanSelectDto selectDto = createSelectDto(query);

        Page<ProdPlan> prodPlansPageResult = baseMapper.selectProdPlanWithTugId(page, selectDto);

        // 构建分页响应
        PageResponseDto<ProPlanVO> pageResponseDto = new PageResponseDto(prodPlansPageResult, ProPlanVO.class);
        List<ProPlanVO> list = pageResponseDto.getRecords();
        List<Long> planIdList = list.stream().map(ProPlanVO::getId).collect(Collectors.toList());

        if (CollectionUtils.isNotEmpty(planIdList)) {
            // 获取所有作业记录
            List<PordWorkVO> workVOList = getWorkVOList(planIdList);

            // 构建计划ID和作业记录之间的映射
            Map<Long, List<PordWorkVO>> workMap = workVOList.stream()
                    .collect(Collectors.groupingBy(PordWorkVO::getPlanId));

            //设置childrenList
            for (ProPlanVO planVO : list) {
                List<PordWorkVO> filterWorkList = workMap.getOrDefault(planVO.getId(), Collections.emptyList());
                planVO.setChildren(filterWorkList);
            }
        }

        pageResponseDto.setRecords(list);
        return pageResponseDto;
    }

    /**
     * 创建查询参数对象
     *
     * @param query 查询参数 DTO
     * @return 查询参数 DTO
     */
    private ProPlanSelectDto createSelectDto(ProPlanQueryDto query) {
        return new ProPlanSelectDto()
                .setPortAreaCode(query.getPortAreaCode())
                .setShipName(query.getShipName())
                .setCustomerName(query.getCustomerName())
                .setQueryType(query.getQueryType())
                .setPlanCode(query.getPlanCode())
                .setPlanTimeStart(query.getPlanTimeStart())
                .setPlanTimeEnd(query.getPlanTimeEnd())
                .setStatus(query.getStatus())
                .setTugId(query.getTugId());
    }

    /**
     * 获取所有作业记录
     *
     * @param planIdList 作业计划 ID 列表
     * @return 作业记录的 VO 列表
     */
    private List<PordWorkVO> getWorkVOList(List<Long> planIdList) {
        List<PordWork> workList = workService.list(new LambdaQueryWrapper<PordWork>().in(PordWork::getPlanId, planIdList));
        return MapperingUtils.copyList(workList, PordWorkVO.class);
    }

    /**
     * 获取费率映射
     *
     * @return 费率映射，以贸易类型和船舶类型为键
     */
    private Map<String, List<MRate>> getRateMap() {
        return rateService.list().stream()
                .collect(Collectors.groupingBy(rate -> rate.getTradeType() + "_" + rate.getShipKindCode()));
    }

    /**
     * 判断船长是否在费率的长度范围内
     * <p>
     * 费率的长度范围定义为 [lengthStart, lengthEnd)
     * 即：船长需要大于等于 lengthStart 且小于 lengthEnd
     *
     * @param rate       费率
     * @param shipLength 船长
     * @return 如果在范围内则返回 true
     */
    private boolean isShipLengthInRange(MRate rate, BigDecimal shipLength) {
        return shipLength.compareTo(BigDecimal.valueOf(rate.getLengthStart())) >= 0
                && shipLength.compareTo(BigDecimal.valueOf(rate.getLengthEnd())) < 0;
    }


    /**
     * 拖轮计费菜单计算【总费用】
     *
     * @param query 查询
     * @return fee
     */
    @Override
    public String totalAmount(ProPlanQueryDto query) {
        String totalAmount = baseMapper.sumAmountByAllPordWork(query);
        return StringUtils.isNotBlank(totalAmount) ? totalAmount : "0";
    }

    @Override
    public ProPlanVO detail(Long id) {
        ProdPlan plan = getById(id);
        ProPlanVO vo = MapperingUtils.copyProperties(plan, ProPlanVO.class);
        List<PordWork> list = workService.list(new LambdaQueryWrapper<PordWork>().eq(PordWork::getPlanId, id));
        List<PordWorkVO> copyList = MapperingUtils.copyList(list, PordWorkVO.class);
        vo.setChildren(copyList);
        return vo;
    }

    /**
     * 新增或更新作业计划
     * <p>
     * 具体步骤如下：
     * <p>
     * 1. 将传入的作业计划参数对象（DTO）转换为实体（ProdPlan），
     * 并设置通用的更新时间、操作人信息，以及作业计划的时间信息。
     * <p>
     * 2. 根据 DTO 中提供的 ID（如果存在），生成或获取作业计划的唯一 ID。
     * 如果 DTO 中的 ID 为 null，则使用 IdWorker 生成新的 ID。
     * <p>
     * 3. 如果是新增操作（即 DTO 中的 ID 为 null）：
     * 3.1 生成作业计划的唯一编号（planCode），并设置其他创建信息。
     * 3.2 保存新增的作业计划到数据库。
     * <p>
     * 4. 如果是更新操作（即 DTO 中的 ID 不为 null）：
     * 4.1 使用 DTO 中提供的 ID 更新已有的作业计划。
     * <p>
     * 5. 处理作业计划的子项（拖轮信息）列表：
     * 5.1 如果子项列表不为空，首先查询并删除与当前作业计划 ID 关联的已有作业记录。
     * 5.2 将新的子项数据转换为作业记录对象，并准备批量保存。
     * 5.3 批量保存新的作业记录到数据库。
     *
     * @param dto 作业计划数据传输对象，包括作业计划信息和子项（拖轮信息）。
     */
    @Override
    public void saveOrUpdatePlan(ProPlanSaveReqDto dto) {
        ProdPlan plan = MapperingUtils.copyProperties(dto, ProdPlan.class)
                .setUpdateTime(LocalDateTime.now())
                .setPlanTime(DateUtils.str2LocalDate(dto.getPlanTime()))
                .setUpdateBy(DefaultCreateByConstant.userId)
                .setUpdateByName(DefaultCreateByConstant.userId.toString());


        List<ProPlanSaveReqDto.PlanTugInfo> relativeTugList = dto.getChildren();
        workService.remove(new LambdaQueryWrapper<PordWork>().eq(PordWork::getPlanId, plan.getId()));
        if (dto.getId() == null) {
            String planCode = GenerateNumberUtil.generateNoByCurrentDate(RedisKeyConstant.PROD_PLAN_CODE_KEY, 3);
            plan.setPlanCode(planCode)
                    .setCreateBy(DefaultCreateByConstant.userId)
                    .setCreateByName(DefaultCreateByConstant.userId.toString())
                    .setCreateTime(LocalDateTime.now())
                    .setId(IdWorker.getId());
            save(plan);
        } else {
            plan.setId(dto.getId());
            updateById(plan);
        }

        List<PordWork> insertWorkList = relativeTugList.stream().map(planTugInfo -> new PordWork()
                .setPlanId(plan.getId())
                .setTugId(planTugInfo.getTugId())
                .setTugNo(planTugInfo.getTugNo())
                .setWorkItemId(planTugInfo.getWorkItemId())
                .setWorkItemName(planTugInfo.getWorkItemName())
                .setStatus(0) // 新增状态默认设为 0
                .setCreateBy(DefaultCreateByConstant.userId)
                .setCreateByName(DefaultCreateByConstant.userId.toString())
                .setCreateTime(LocalDateTime.now())
                .setUpdateTime(LocalDateTime.now())
                .setUpdateBy(DefaultCreateByConstant.userId)
                .setUpdateByName(DefaultCreateByConstant.userId.toString())).collect(Collectors.toList());

        if (CollectionUtils.isNotEmpty(insertWorkList)) {
            //计算费用
            MShip ship = shipService.getById(plan.getShipId());

            BigDecimal shipLength = ship.getShipLength();
            String shipType = ship.getShipKindCode();
            String tradeType = plan.getTradeType();

            // 在内存中过滤费率数据
            List<MRate> filteredRates = getRateMap().getOrDefault(tradeType + "_" + shipType, Collections.emptyList()).stream()
                    .filter(rate -> isShipLengthInRange(rate, shipLength))
                    .collect(Collectors.toList());

            List<ProdPlan> updatePlanList = Lists.newArrayList();
            if (CollectionUtils.isNotEmpty(filteredRates)) {
                BigDecimal planAmount = BigDecimal.ZERO;
                for (PordWork work : insertWorkList) {
                    work.setAmount(BigDecimal.valueOf(filteredRates.get(0).getRate()));
                    work.setCalculateRemark(
                            String.format("费率：%s。 船舶类型：%s, 长度：%s, 区间：%s - %s, 贸别：%s",
                                    filteredRates.get(0).getRate(), filteredRates.get(0).getShipKindName(), shipLength, filteredRates.get(0).getLengthStart(), filteredRates.get(0).getLengthEnd(),
                                    RateConstant.NM_KEY.equals(filteredRates.get(0).getTradeType()) ? RateConstant.NM_VAL : RateConstant.WM_VAL)
                    );
                    planAmount = planAmount.add(BigDecimal.valueOf(filteredRates.get(0).getRate()));
                }
                plan.setFee(planAmount);
                plan.setCalculateRemark(String.format("%s(总额) = %s(费率) * %s(拖轮数)", planAmount, filteredRates.get(0).getRate(), insertWorkList.size()));
                updatePlanList.add(plan);
            } else {
                throw new BusinessRuntimeException(BizCode.SYSTEM_ERROR, "未找到匹配的费率，拖轮作业记录ID：" + dto.getId());
            }
            updateBatchById(updatePlanList);
            workService.saveBatch(insertWorkList);
        }
    }

    /**
     * 批量审核/反审核
     *
     * @param dto dtoParam
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approveBatch(ProPlanApproveBatchReqDto dto) {

        //【拖轮计费】菜单，如果点了审核，则要保证已经填写了作业记录的完工信息才行，不然数据是空的
        //即:想从已审核变更为已计费状态，要保证拖轮开始结束时间、油耗等信息都写了
        if (ProdPlanStatusConstant.FEE.equals(dto.getStatus())) {
            List<PordWork> workList = workService.list(new LambdaQueryWrapper<PordWork>().in(PordWork::getPlanId, dto.getIds()));
            Map<Long, List<PordWork>> workMapByPlanId = workList.stream().collect(Collectors.groupingBy(PordWork::getPlanId));
            for (Long planId : workMapByPlanId.keySet()) {
                List<PordWork> list = workMapByPlanId.get(planId);
                for (PordWork work : list) {
                    if (Objects.isNull(work.getBeginTime()) || Objects.isNull(work.getEndTime())) {
                        throw new BusinessRuntimeException(BizCode.BIZ_ERROR, "作业计划:" + getById(planId).getPlanCode() + "的拖轮完工信息不完整");
                    }
                }
            }
        }

        //状态更新前的校验
        List<ProdPlan> plansToUpdate = list(new LambdaQueryWrapper<ProdPlan>().in(ProdPlan::getId, dto.getIds()));

        for (ProdPlan plan : plansToUpdate) {
//            if (ProdPlanStatusConstant.FEE.equals(plan.getStatus()) && dto.getStatus().equals(ProdPlanStatusConstant.APPROVED)) {
//                throw new BusinessRuntimeException(BizCode.BIZ_ERROR, "已计费的数据不能进行审核操作");
//            }
            // 只有待审核的作业计划才能变更为已审核状态，其他状态如已计费/已审核等不能重复设置为已审核
            // 想设置为已审核  && 当前数据的状态 != 待审核时
//            if (ProdPlanStatusConstant.APPROVED.equals(dto.getStatus()) && !plan.getStatus().equals(ProdPlanStatusConstant.WAIT_APPROVE)) {
//                throw new BusinessRuntimeException(BizCode.BIZ_ERROR, "只有待审核的计划才能进行审核操作");
//            }
        }
        List<ProdPlan> updateList = dto.getIds().stream()
                .map(id -> new ProdPlan().setId(id).setStatus(dto.getStatus()).setUpdateTime(LocalDateTime.now()))
                .collect(Collectors.toList());
        updateBatchById(updateList);
    }

    /**
     * 拖轮计费
     * <p>
     * 1、根据计划中的shipId，找寻船舶
     * 2、找到船舶后，根据大船规范里的船长度shipLength
     * 3、到拖轮费率里，根据shipLength处于哪个范围，获取到“费率”字段，更新到这个计划的amount中
     * 4、计算完成后，更新plan的状态为2已计费
     *
     * @param dtoList dtoList
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void tugCalculateFee(List<TugCalculateFeeReqDto> dtoList) {
        if (CollectionUtils.isEmpty(dtoList)) {
            throw new BusinessRuntimeException(BizCode.REQUIRED_PARAM_MISS, "至少选择一条记录");
        }
        // 批量获取所有拖轮作业记录
        List<PordWork> workList = getWorkList(dtoList);
        Map<Long, PordWork> workMap = workList.stream().collect(Collectors.toMap(PordWork::getId, work -> work));

        // 批量获取所有作业计划
        List<ProdPlan> planList = getPlanList(workList);
        Map<Long, ProdPlan> planMap = planList.stream().collect(Collectors.toMap(ProdPlan::getId, plan -> plan));

        // 批量获取所有船舶信息
        Map<Long, MShip> shipMap = getShipMapByPlanList(planList);

        // 一次性获取所有费率数据
        Map<String, List<MRate>> rateMap = getRateMap();

        // 初始化需要更新的作业记录、计划和附件
        List<PordWork> worksToUpdate = new ArrayList<>();
        Set<ProdPlan> plansToUpdate = new HashSet<>();

        // 处理附件更新
        List<SysFileBusiness> insertFileList = new ArrayList<>();
        Set<Long> businessIdsToRemove = new HashSet<>();

        // 处理拖轮计费
        processTugCalculateFee(dtoList, workMap, planMap, shipMap, rateMap, worksToUpdate, plansToUpdate, insertFileList, businessIdsToRemove);

        // 批量更新作业记录和计划
        updateWorksAndPlans(worksToUpdate, plansToUpdate);

        // 批量删除旧的附件并上传新的附件
        updateFiles(businessIdsToRemove, insertFileList);

        //某一条记录完工成功后，把当前计划的状态改为【完工】
        //20240820修正： 如果该计划， 其他的作业记录也都填写完工信息了，才设为【完工状态】
        //setPlanStatusToFinishWork(dtoList); //TODO 2024-09-11 liPeng 暂时注释掉自动完工，改为手动完工
    }

    /**
     * 计划的状态改为【完工】
     *
     * @param dtoList 作业记录
     */
    private void setPlanStatusToFinishWork(List<TugCalculateFeeReqDto> dtoList) {
        TugCalculateFeeReqDto dto = dtoList.get(0);
        Long planId = workService.getById(dto.getId()).getPlanId();

        // 统计符合条件且 beginTime 不为空的记录数
        long count = workService.count(new LambdaQueryWrapper<PordWork>()
                .eq(PordWork::getPlanId, planId)
                .ne(PordWork::getId, dto.getId())
                .isNull(PordWork::getBeginTime));

        // 如果 count 为 0，说明该计划下其他记录的 beginTime 字段都不为空，即都已经填写完工信息了
        if (count == 0) {
            // 当其他记录的完工信息都不为空时，更新当前计划 status 为 完工
            LambdaUpdateWrapper<ProdPlan> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(ProdPlan::getId, planId)
                    .set(ProdPlan::getStatus, ProdPlanStatusConstant.FINISH_WORK);
            baseMapper.update(null, updateWrapper);
        }
    }

    /**
     * 从 DTO 列表中提取作业记录 ID 并批量获取作业记录。
     *
     * @param dtoList 包含作业记录 ID 的 DTO 列表
     * @return 作业记录列表
     */
    private List<PordWork> getWorkList(List<TugCalculateFeeReqDto> dtoList) {
        List<Long> workIds = dtoList.stream().map(TugCalculateFeeReqDto::getId).collect(Collectors.toList());
        return workService.list(new LambdaQueryWrapper<PordWork>().in(PordWork::getId, workIds));
    }

    /**
     * 从作业记录列表中提取作业计划 ID 并批量获取作业计划。
     *
     * @param workList 作业记录列表
     * @return 作业计划列表
     */
    private List<ProdPlan> getPlanList(List<PordWork> workList) {
        List<Long> planIds = workList.stream().map(PordWork::getPlanId).distinct().collect(Collectors.toList());
        return list(new LambdaQueryWrapper<ProdPlan>().in(ProdPlan::getId, planIds));
    }

    /**
     * 从作业计划列表中提取船舶 ID 并批量获取船舶信息。
     *
     * @param planList 作业计划列表
     * @return 船舶信息的映射，以船舶 ID 为键
     */
    private Map<Long, MShip> getShipMapByPlanList(List<ProdPlan> planList) {
        List<Long> shipIds = planList.stream().map(ProdPlan::getShipId).distinct().collect(Collectors.toList());
        List<MShip> shipList = shipService.list(new LambdaQueryWrapper<MShip>().in(MShip::getId, shipIds));
        return shipList.stream().collect(Collectors.toMap(MShip::getId, ship -> ship));
    }

    /**
     * 设置作业记录的更新信息。
     *
     * @param work         要更新的作业记录
     * @param dto          包含更新数据的 DTO
     * @param selectedRate 选中的费率
     */
    private void setUpdateWorkInfo(PordWork work, TugCalculateFeeReqDto dto, MRate selectedRate) {
        work.setAmount(BigDecimal.valueOf(selectedRate.getRate()));  // 设置金额
        work.setBeginTime(DateUtils.str2LocalDateTime(dto.getBeginTime()));  // 设置开始时间
        work.setEndTime(DateUtils.str2LocalDateTime(dto.getEndTime()));  // 设置结束时间
        work.setOilConsume(dto.getOilConsume());  // 设置油耗
        work.setDistance(dto.getDistance());  // 设置距离
        work.setUpdateTime(LocalDateTime.now());  // 设置更新时间
        work.setUpdateBy(DefaultCreateByConstant.userId);  // 设置更新人 ID
        work.setUpdateByName(DefaultCreateByConstant.userId.toString());  // 设置更新人姓名
        work.setWorkHours(DateUtils.calculateDurationInHours(DateUtils.str2LocalDateTime(dto.getBeginTime()), DateUtils.str2LocalDateTime(dto.getEndTime())));  // 计算并设置工时
    }

    /**
     * 处理拖轮计费逻辑，包括更新作业记录、标记计划为已计费以及处理附件。
     *
     * @param dtoList             包含拖轮计费请求数据的 DTO 列表
     * @param workMap             作业记录 ID 到作业记录的映射
     * @param planMap             作业计划 ID 到作业计划的映射
     * @param shipMap             作业计划中船舶 ID 到船舶信息的映射
     * @param rateMap             费率数据的映射，以贸易类型和船舶类型为键
     * @param worksToUpdate       需要更新的作业记录列表
     * @param plansToUpdate       需要更新的作业计划集合
     * @param insertFileList      需要插入的附件业务对象列表
     * @param businessIdsToRemove 需要删除的业务 ID 集合
     */
    private void processTugCalculateFee(List<TugCalculateFeeReqDto> dtoList, Map<Long, PordWork> workMap, Map<Long, ProdPlan> planMap,
                                        Map<Long, MShip> shipMap, Map<String, List<MRate>> rateMap, List<PordWork> worksToUpdate,
                                        Set<ProdPlan> plansToUpdate, List<SysFileBusiness> insertFileList, Set<Long> businessIdsToRemove) {
        for (TugCalculateFeeReqDto dto : dtoList) {
            // 获取作业记录、作业计划和船舶信息
            PordWork work = workMap.get(dto.getId());
            if (work == null) {
                throw new BusinessRuntimeException(BizCode.SYSTEM_ERROR, "拖轮作业记录不存在，ID：" + dto.getId());
            }

            ProdPlan plan = planMap.get(work.getPlanId());
            if (plan == null) {
                throw new BusinessRuntimeException(BizCode.SYSTEM_ERROR, "作业计划不存在，ID：" + work.getPlanId());
            }

            MShip ship = shipMap.get(plan.getShipId());
            if (ship == null) {
                throw new BusinessRuntimeException(BizCode.SYSTEM_ERROR, "船舶信息不存在，计划ID：" + plan.getId());
            }

            BigDecimal shipLength = ship.getShipLength();
            String shipType = ship.getShipKindCode();
            String tradeType = plan.getTradeType();

            // 在内存中过滤费率数据
            List<MRate> filteredRates = rateMap.getOrDefault(tradeType + "_" + shipType, Collections.emptyList()).stream()
                    .filter(rate -> isShipLengthInRange(rate, shipLength))
                    .collect(Collectors.toList());

            if (CollectionUtils.isNotEmpty(filteredRates)) {
                setUpdateWorkInfo(work, dto, filteredRates.get(0));  // 更新作业记录信息
                worksToUpdate.add(work);  // 收集需要更新的 work 对象
            } else {
                throw new BusinessRuntimeException(BizCode.SYSTEM_ERROR, "未找到匹配的费率，拖轮作业记录ID：" + dto.getId());
            }

            // 如果所有相关作业记录都已更新费率，则标记计划为已计费
            boolean allBilled = workMap.values().stream()
                    .filter(w -> w.getPlanId().equals(plan.getId()))
                    .allMatch(w -> w.getAmount() != null);
            if (allBilled) {
                plansToUpdate.add(plan);  // 收集需要更新的 plan 对象
            }

            // 处理附件更新，收集要删除的业务ID和要插入的附件数据
            businessIdsToRemove.add(dto.getId());
            insertFileList.addAll(createFileBusinessList(dto));
        }
    }

    /**
     * 创建附件业务对象列表。
     *
     * @param dto 包含附件 ID 列表的 DTO
     * @return 附件业务对象列表
     */
    private List<SysFileBusiness> createFileBusinessList(TugCalculateFeeReqDto dto) {
        return dto.getAttachList().stream().map(attachId ->
                new SysFileBusiness()
                        .setBusinessId(dto.getId())
                        .setFileId(attachId)
                        .setCreateTime(LocalDateTime.now())
                        .setUpdateTime(LocalDateTime.now())
        ).collect(Collectors.toList());
    }

    /**
     * 批量更新作业记录和计划。
     *
     * @param worksToUpdate 需要更新的作业记录列表
     * @param plansToUpdate 需要更新的作业计划集合
     */
    private void updateWorksAndPlans(List<PordWork> worksToUpdate, Set<ProdPlan> plansToUpdate) {
        if (CollectionUtils.isNotEmpty(worksToUpdate)) {
            workService.updateBatchById(worksToUpdate);
        }
        if (CollectionUtils.isNotEmpty(plansToUpdate)) {
            updateBatchById(new ArrayList<>(plansToUpdate));
        }
    }

    /**
     * 批量删除和新增附件。
     *
     * @param businessIdsToRemove 需要删除的业务 ID 集合
     * @param insertFileList      需要插入的附件业务对象列表
     */
    private void updateFiles(Set<Long> businessIdsToRemove, List<SysFileBusiness> insertFileList) {
        if (CollectionUtils.isNotEmpty(businessIdsToRemove)) {
            fileBusinessService.remove(new LambdaQueryWrapper<SysFileBusiness>().in(SysFileBusiness::getBusinessId, businessIdsToRemove));
        }
        if (CollectionUtils.isNotEmpty(insertFileList)) {
            fileBusinessService.saveBatch(insertFileList);
        }
    }

    //作业计划/计费导出
    @Override
    public List<ProPlanExportDto> exportPlanList(ProPlanQueryDto query) {
        long currentPage = 1;
        List<ProPlanExportDto> allRecords = Lists.newArrayList();

        while (true) {
            query.setStartPage(currentPage);
            PageResponseDto<ProPlanVO> page = this.treeList(query);
            List<ProPlanVO> records = page.getRecords();

            for (ProPlanVO vo : records) {
                ProPlanExportDto exportDto = MapperingUtils.copyProperties(vo, ProPlanExportDto.class);
                List<PordWorkVO> voChildren = vo.getChildren();
                List<PordWorkExportDto> workExportDtoList = MapperingUtils.copyList(voChildren, PordWorkExportDto.class);
                exportDto.setChildren(workExportDtoList);
                allRecords.add(exportDto);
            }
            if (records.size() < query.getPageSize()) {
                // 当前页的记录数小于 pageSize，说明已经是最后一页
                break;
            }
            currentPage++;
        }

        return allRecords;
    }
}
